001    package net.sf.xdc.processing;
002    
003    /*
004     *  Copyright 2005-2006 Jens Voß.
005     *
006     *  Licensed under the GNU Lesser General Public License (the "License");
007     *  you may not use this file except in compliance with the License.
008     *  You may obtain a copy of the License at
009     *
010     *       http://opensource.org/licenses/lgpl-license.php
011     *
012     *  Unless required by applicable law or agreed to in writing, software
013     *  distributed under the License is distributed on an "AS IS" BASIS,
014     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     *  See the License for the specific language governing permissions and
016     *  limitations under the License.
017     */
018    
019    import java.io.File;
020    import java.io.FileInputStream;
021    import java.io.FileNotFoundException;
022    import java.io.FileOutputStream;
023    import java.io.IOException;
024    import java.io.InputStream;
025    import java.io.OutputStream;
026    import java.io.Writer;
027    import java.util.Properties;
028    import java.util.Set;
029    import java.util.HashSet;
030    import java.util.Iterator;
031    import java.util.Map;
032    import java.util.HashMap;
033    import java.util.Locale;
034    import java.util.StringTokenizer;
035    import java.util.ResourceBundle;
036    import java.util.Enumeration;
037    import java.util.List;
038    import java.util.Vector;
039    import java.nio.charset.Charset;
040    import java.nio.charset.UnsupportedCharsetException;
041    
042    import javax.xml.transform.TransformerException;
043    
044    import net.sf.xdc.util.Logging;
045    import net.sf.xdc.util.XmlException;
046    import net.sf.xdc.util.XmlUtils;
047    import net.sf.xdc.util.XslUtils;
048    import net.sf.xdc.util.IOUtils;
049    import net.sf.xdc.util.XPathUtils;
050    import org.apache.commons.cli.CommandLine;
051    import org.apache.log4j.Logger;
052    import org.w3c.dom.Document;
053    import org.w3c.dom.Element;
054    import org.w3c.dom.Node;
055    import org.w3c.dom.traversal.NodeIterator;
056    
057    /**
058     * This class is responsible for processing all source files by applying the
059     * appropriate stylesheets to the sources specified during the invocation of
060     * the XDC tool.
061     *
062     * @author Jens Voß
063     * @since 0.5
064     * @version 0.5
065     */
066    public class XdcProcessor {
067    
068      private static final Logger LOG = Logging.getLogger();
069    
070      private static final String XSL_PKG = "net/sf/xdc/xsl";
071      private static final String RESOURCE_PKG = "net/sf/xdc/resources";
072    
073      private CommandLine line;
074      private XdcSourceCollector sourceCollector;
075      private File outputDir;
076      private Properties baseProperties;
077      private Map tables; // Map<String, Properties>
078      private List patterns; // List<Pattern>
079      private SourceProcessor sourceProcessor;
080      private Charset encoding;
081      private Charset docencoding;
082      private Locale locale = Locale.US;
083    
084      /**
085       * This public constructor uses the command line to collect all necessary
086       * sources for processing.
087       *
088       * @param line The command line which specifies which sources are to be
089       *         processed and details on how they should be processed
090       */
091      public XdcProcessor(CommandLine line) {
092        this.line = line;
093        sourceCollector = new XdcSourceCollector(line);
094        if (line.hasOption("d")) {
095          outputDir = new File(line.getOptionValue("d"));
096        }
097        else {
098          outputDir = sourceCollector.getLeadingSourcePath();
099        }
100        if (!IOUtils.makeEmpty(outputDir)) {
101          LOG.warn("Output directory " + outputDir.getPath() + " could not be emptied.");
102        }
103        baseProperties = XdcOptions.getXdcOptions().getOptionProperties(line);
104        baseProperties.setProperty("framesetsize", String.valueOf(sourceCollector.getFramesetSize()));
105        tables = new HashMap(3);
106        tables.put("config", baseProperties);
107        if (line.hasOption("linksource")) {
108          sourceProcessor = new SourceProcessor(outputDir);
109        }
110        if (line.hasOption("encoding")) {
111          String value = line.getOptionValue("encoding");
112          try {
113            encoding = Charset.forName(value);
114            LOG.info("Using '" + value + "' for source file encoding.");
115          }
116          catch (UnsupportedCharsetException e) {
117            LOG.warn("Source file encoding '" + value + "' not recognized - using default.");
118          }
119        }
120        else {
121          LOG.info("No source file encoding specified - using default.");
122        }
123        if (line.hasOption("docencoding")) {
124          String value = line.getOptionValue("docencoding");
125          try {
126            docencoding = Charset.forName(value);
127            LOG.info("Using '" + value + "' for target file encoding.");
128          }
129          catch (UnsupportedCharsetException e) {
130            LOG.warn("Target file encoding '" + value + "' not recognized - using default.");
131          }
132        }
133        else {
134          if (encoding == null) {
135            LOG.info("No target file encoding specified - using default.");
136          }
137          else {
138            docencoding = encoding;
139            LOG.info("No target file encoding specified - using source file encoding (" + docencoding.name() + ").");
140          }
141        }
142        if (line.hasOption("locale")) {
143          StringTokenizer tok = new StringTokenizer(line.getOptionValue("locale"), "_", false);
144          switch (tok.countTokens()) {
145            case 1: locale = new Locale(tok.nextToken()); break;
146            case 2: locale = new Locale(tok.nextToken(), tok.nextToken()); break;
147            case 3: locale = new Locale(tok.nextToken(), tok.nextToken(), tok.nextToken()); break;
148          }
149        }
150        LOG.info("Using locale " + locale.toString());
151        Locale.setDefault(locale);
152        ResourceBundle bundle = ResourceBundle.getBundle("net.sf.xdc.resources.xdc", locale);
153        Properties dictionary = new Properties();
154        for (Enumeration keys = bundle.getKeys(); keys.hasMoreElements();) {
155          String key = (String) keys.nextElement();
156          dictionary.setProperty(key, bundle.getString(key));
157        }
158        tables.put("dictionary", dictionary);
159        patterns = new Vector();
160      }
161    
162      /**
163       * This method generates all files for the XDC documentation pages.
164       */
165      public void process() {
166        processStylesheetFile();
167        processHelpFile();
168        preprocessBasicContent();
169        processBasicContent();
170        processSupportFiles();
171      }
172    
173      private void preprocessBasicContent() {
174        XdcSource[] sources = sourceCollector.getXdcSources();
175        Set sourceFiles = new HashSet(); // Set<String>
176        for (int i = 0; i < sources.length; i++) {
177          XdcSource source = sources[i];
178          try {
179            Document doc = XslUtils.transform(source.getFile().getAbsolutePath(), encoding, XSL_PKG + "/patterns.xsl", tables);
180            NodeIterator iter = XPathUtils.selectNodes(doc, "//pattern");
181            Node patternNode;
182            while ((patternNode = iter.nextNode()) != null) {
183              String file = XPathUtils.selectNode(patternNode, "file/text()").getNodeValue();
184              String xpath = XmlUtils.getTextValue(XPathUtils.selectNode(patternNode, "xpath"));
185              XPathPattern pattern = new XPathPattern(file, xpath);
186              String sourceFile;
187              if (file == null || file.length() == 0) {
188                sourceFile = source.getSourceFileName();
189              }
190              else if (file.indexOf('/') < 0) {
191                sourceFile = source.getPackageName() + '/' + file;
192              }
193              else {
194                sourceFile = file;
195              }
196              pattern.setTargetFile(sourceFile);
197              sourceFiles.add(sourceFile);
198              patterns.add(pattern);
199              source.addPattern(pattern);
200            }
201          }
202          catch (XmlException e) {
203            LOG.error(e.getMessage(), e);
204          }
205          catch (TransformerException e) {
206            LOG.error(e.getMessage(), e);
207          }
208        }
209        for (Iterator iter = sourceFiles.iterator(); iter.hasNext();) {
210          String sourceFile = (String) iter.next();
211          for (Iterator pIter = patterns.iterator(); pIter.hasNext();) {
212            XPathPattern pattern = (XPathPattern) pIter.next();
213            if (pattern.getTargetFile().equals(sourceFile)) {
214              XdcSource source = sourceCollector.getXdcSource(sourceFile);
215              if (source == null) {
216                continue;
217              }
218              FileInputStream in = null;
219              try {
220                in = new FileInputStream(source.getFile());
221                Document doc = XmlUtils.parse(in);
222                Node node = XPathUtils.selectNode(doc, pattern.getPattern());
223                if (node != null) {
224                  String link = XmlUtils.getLink((Element) node);
225                  pattern.setValue(link);
226                }
227              }
228              catch (FileNotFoundException e) {
229                LOG.error(e.getMessage(), e);
230              }
231              catch (XmlException e) {
232                LOG.error(e.getMessage(), e);
233              }
234              catch (TransformerException e) {
235                LOG.error(e.getMessage(), e);
236              }
237              finally {
238                if (in != null) {
239                  try {
240                    in.close();
241                  }
242                  catch (IOException e) {
243                    e.printStackTrace();
244                  }
245                }
246              }
247            }
248          }
249        }
250      }
251    
252      private void processBasicContent() {
253        String[] packageNames = sourceCollector.getPackageNames();
254        for (int i = 0; i < packageNames.length; i++) {
255    
256          XdcSource[] sources = sourceCollector.getXdcSources(packageNames[i]);
257          File dir = new File(outputDir, packageNames[i]);
258          dir.mkdirs();
259          Set pkgDirs = new HashSet();
260    
261          for (int j = 0; j < sources.length; j++) {
262            XdcSource source = sources[j];
263            // set the xpath patterns
264            Properties patterns = source.getPatterns();
265            if (patterns != null) {
266              tables.put("patterns", patterns);
267            }
268            pkgDirs.add(source.getFile().getParentFile());
269            DialectHandler dialectHandler = source.getDialectHandler();
270            Writer out = null;
271            try {
272              String sourceFilePath = source.getFile().getAbsolutePath();
273              source.setProcessingProperties(baseProperties,
274                                             j > 0 ? sources[j-1].getFileName() : null,
275                                             j < sources.length - 1 ? sources[j+1].getFileName() : null);
276              // set the root comment for use in package-summary page
277              String rootComment = XslUtils.transformToString(sourceFilePath,
278                                                              XSL_PKG + "/source-header.xsl",
279                                                              tables);
280              source.setRootComment(rootComment);
281              // then do the actual processing
282              Document htmlDoc = XslUtils.transform(sourceFilePath,
283                                                    encoding,
284                                                    dialectHandler.getXslResourcePath(),
285                                                    tables);
286              if (LOG.isInfoEnabled()) {
287                LOG.info("Processing source file " + sourceFilePath);
288              }
289              out = IOUtils.getWriter(new File(dir, source.getFile().getName() + ".html"), docencoding);
290              XmlUtils.printHtml(out, htmlDoc);
291              if (sourceProcessor != null) {
292                sourceProcessor.processSource(source, encoding, docencoding);
293              }
294              XdcSource.clearProcessingProperties(baseProperties);
295            }
296            catch (XmlException e) {
297              LOG.error(e.getMessage(), e);
298            }
299            catch (IOException e) {
300              LOG.error(e.getMessage(), e);
301            }
302            finally {
303              if (out != null) {
304                try {
305                  out.close();
306                }
307                catch (IOException e) {
308                  LOG.error(e.getMessage(), e);
309                }
310              }
311            }
312          }
313          for (Iterator iter = pkgDirs.iterator(); iter.hasNext();) {
314            File docFilesDir = new File((File) iter.next(), "xdc-doc-files");
315            if (docFilesDir.exists() && docFilesDir.isDirectory()) {
316              File targetDir = new File(dir, "xdc-doc-files");
317              targetDir.mkdirs();
318              try {
319                IOUtils.copyTree(docFilesDir, targetDir);
320              }
321              catch (IOException e) {
322                LOG.error(e.getMessage(), e);
323              }
324            }
325          }
326        }
327      }
328    
329      private void processSupportFiles() {
330    
331        processAllclassesFrame();
332        processFrameset();
333    
334        if (sourceCollector.getFramesetSize() == 3) {
335          processOverviewFiles();
336        }
337    
338        String[] packageNames = this.sourceCollector.getPackageNames();
339        for (int i = 0; i < packageNames.length; i++) {
340          processPackageFiles(packageNames, i);
341        }
342    
343      }
344    
345      private void processStylesheetFile() {
346        InputStream in = null;
347        OutputStream out = null;
348        try {
349          String filename;
350          if (line.hasOption("stylesheetfile")) {
351            filename = line.getOptionValue("stylesheetfile");
352            in = new FileInputStream(filename);
353            filename = filename.replace('\\', '/');
354            filename = filename.substring(filename.lastIndexOf('/') + 1);
355          }
356          else {
357            ClassLoader cl = this.getClass().getClassLoader();
358            filename = "stylesheet.css";
359            in = cl.getResourceAsStream(RESOURCE_PKG + "/stylesheet.css");
360          }
361          baseProperties.setProperty("stylesheetfilename", filename);
362          File outFile = new File(outputDir, filename);
363          if (LOG.isInfoEnabled()) {
364            LOG.info("Processing generated file " + outFile.getAbsolutePath());
365          }
366          out = new FileOutputStream(outFile);
367          IOUtils.copy(in, out);
368        }
369        catch (FileNotFoundException e) {
370          LOG.error(e.getMessage(), e);
371        }
372        catch (IOException e) {
373          LOG.error(e.getMessage(), e);
374        }
375        finally {
376          if (in != null) {
377            try {
378              in.close();
379            }
380            catch (IOException e) {
381              LOG.error(e.getMessage(), e);
382            }
383          }
384          if (out != null) {
385            try {
386              out.close();
387            }
388            catch (IOException e) {
389              LOG.error(e.getMessage(), e);
390            }
391          }
392        }
393      }
394    
395      private void processHelpFile() {
396        InputStream in = null;
397    
398        if (line.hasOption("helpfile")) {
399          OutputStream out = null;
400          try {
401            String filename = line.getOptionValue("helpfile");
402            in = new FileInputStream(filename);
403            filename = filename.replace('\\', '/');
404            filename = filename.substring(filename.lastIndexOf('/') + 1);
405            File outFile = new File(outputDir, filename);
406            if (LOG.isInfoEnabled()) {
407              LOG.info("Processing generated file " + outFile.getAbsolutePath());
408            }
409            out = new FileOutputStream(outFile);
410            baseProperties.setProperty("helpfile", filename);
411            IOUtils.copy(in, out);
412          }
413          catch (FileNotFoundException e) {
414            LOG.error(e.getMessage(), e);
415          }
416          catch (IOException e) {
417            LOG.error(e.getMessage(), e);
418          }
419          finally {
420            if (in != null) {
421              try {
422                in.close();
423              }
424              catch (IOException e) {
425                LOG.error(e.getMessage(), e);
426              }
427            }
428            if (out != null) {
429              try {
430                out.close();
431              }
432              catch (IOException e) {
433                LOG.error(e.getMessage(), e);
434              }
435            }
436          }
437          return;
438        }
439    
440        Properties props = new Properties(baseProperties);
441        ClassLoader cl = this.getClass().getClassLoader();
442        Document doc = null;
443        try {
444          in = cl.getResourceAsStream(RESOURCE_PKG + "/help-doc.xml");
445          doc = XmlUtils.parse(in);
446        }
447        catch (XmlException e) {
448          LOG.error(e.getMessage(), e);
449        }
450        finally {
451          if (in != null) {
452            try {
453              in.close();
454            }
455            catch (IOException e) {
456              LOG.error(e.getMessage(), e);
457            }
458          }
459        }
460        transformResource(doc, "help-doc.xsl", props, "help-doc.html");
461        baseProperties.setProperty("helpfile", "help-doc.html");
462      }
463    
464      private void processAllclassesFrame() {
465        Properties props = new Properties(baseProperties);
466        try {
467          XdcSource[] sources = this.sourceCollector.getXdcSources();
468          Document xml = XmlUtils.createDocument();
469          Element sourcesNode = xml.createElement("sources");
470          xml.appendChild(sourcesNode);
471          for (int i = 0; i < sources.length; i++) {
472            Element sourceNode = xml.createElement("source");
473            sourceNode.setAttribute("name", sources[i].getFile().getName());
474            sourceNode.setAttribute("package", sources[i].getPackageName());
475            sourcesNode.appendChild(sourceNode);
476          }
477          transformResource(xml, "allclasses-frame.xsl", props, "allclasses-noframe.html");
478          props.setProperty("targetFrame", "classFrame");
479          transformResource(xml, "allclasses-frame.xsl", props, "allclasses-frame.html");
480        }
481        catch (XmlException e) {
482          LOG.error(e.getMessage(), e);
483        }
484      }
485    
486      private void processFrameset() {
487        Properties props = new Properties(baseProperties);
488        String indexFileName = "index" + sourceCollector.getFramesetSize() + ".xsl";
489        if (sourceCollector.getFramesetSize() == 2) {
490          props.setProperty("package", sourceCollector.getPackageNames()[0]);
491        }
492        transformResource(indexFileName, props, "index.html");
493      }
494    
495      private void processOverviewFiles() {
496        Properties props = new Properties(baseProperties);
497        props.setProperty("displayLevel", "overview");
498        props.setProperty("summaryText", sourceCollector.getSummaryText());
499        String[] packageNames = sourceCollector.getPackageNames();
500        try {
501          Document xml = XmlUtils.createDocument();
502          Element packagesNode = xml.createElement("packages");
503          xml.appendChild(packagesNode);
504          for (int i = 0; i < packageNames.length; i++) {
505            XdcPackage xdcPackage = sourceCollector.getXdcPackage(packageNames[i]);
506            Element packageNode = xml.createElement("package");
507            packageNode.setAttribute("name", packageNames[i]);
508            packagesNode.appendChild(packageNode);
509            packageNode.appendChild(xml.createTextNode(xdcPackage.getSummaryText()));
510          }
511          transformResource(xml, "overview-frame.xsl", props, "overview-frame.html");
512          transformResource(xml, "overview-summary.xsl", props, "overview-summary.html");
513        }
514        catch (XmlException e) {
515          LOG.error(e.getMessage(), e);
516        }
517      }
518    
519      private void processPackageFiles(String[] packageNames, int i) {
520        XdcPackage xdcPackage = sourceCollector.getXdcPackage(packageNames[i]);
521        Properties props = new Properties(baseProperties);
522        props.setProperty("package", packageNames[i]);
523        props.setProperty("displayLevel", "package");
524        props.setProperty("summaryText", xdcPackage.getSummaryText());
525        if (i == 0) {
526          props.remove("prevFileName");
527        }
528        else {
529          props.setProperty("prevFileName", packageNames[i-1] + "/package-summary.html");
530        }
531        if (i == packageNames.length - 1) {
532          props.remove("nextFileName");
533        }
534        else {
535          props.setProperty("nextFileName", packageNames[i+1] + "/package-summary.html");
536        }
537        XdcSource[] sources = xdcPackage.getXdcSources();
538        try {
539          Document xml = XmlUtils.createDocument();
540          Element sourcesNode = xml.createElement("sources");
541          xml.appendChild(sourcesNode);
542          for (int j = 0; j < sources.length; j++) {
543            Element sourceNode = xml.createElement("source");
544            sourceNode.setAttribute("name", sources[j].getFile().getName());
545            sourcesNode.appendChild(sourceNode);
546            sourceNode.appendChild(xml.createTextNode(sources[j].getRootComment()));
547          }
548          transformResource(xml, "package-frame.xsl", props, packageNames[i] + "/package-frame.html");
549          transformResource(xml, "package-summary.xsl", props, packageNames[i] + "/package-summary.html");
550        }
551        catch (XmlException e) {
552          LOG.error(e.getMessage(), e);
553        }
554      }
555    
556      private void transformResource(String resourceFileName,
557                                     Properties props, String outFileName) {
558        try {
559          transformResource(XmlUtils.createDocument(), resourceFileName, props, outFileName);
560        }
561        catch (XmlException e) {
562          LOG.error(e.getMessage(), e);
563        }
564      }
565    
566      private void transformResource(Document xml, String resourceFileName,
567                                     Properties props, String outFileName) {
568        props.setProperty("outFileName", outFileName);
569        Writer out = null;
570        Properties oldConfig = (Properties) tables.put("config", props);
571        try {
572          Document html = XslUtils.transform(xml, XSL_PKG + '/' + resourceFileName, tables);
573          File outFile = new File(outputDir, outFileName);
574          outFile.getParentFile().mkdirs();
575          out = IOUtils.getWriter(outFile, docencoding);
576          if (LOG.isInfoEnabled()) {
577            LOG.info("Processing generated file " + outFile.getAbsolutePath());
578          }
579          XmlUtils.printHtml(out, html);
580        }
581        catch (FileNotFoundException e) {
582          LOG.error(e.getMessage(), e);
583        }
584        catch (IOException e) {
585          LOG.error(e.getMessage(), e);
586        }
587        catch (XmlException e) {
588          LOG.error(e.getMessage(), e);
589        }
590        finally {
591          if (out != null) {
592            try {
593              out.close();
594            }
595            catch (IOException e) {
596              LOG.error(e.getMessage(), e);
597            }
598          }
599          if (oldConfig != null) {
600            tables.put("config", oldConfig);
601          }
602          else {
603            tables.remove("config");
604          }
605        }
606      }
607    
608    }